# 比特币的网络IO部分我打算做成一个socket通信的，这样能够自定义很多东西并且可以避免恼人的前端开发
# TODO 这部分写的比较快，考虑的东西也相对较少，比如说典型的，需要考虑一下消息传输加密问题，以及后面有单独TODO的来源验证问题。
from blockchain_main import Blockchain
import socket
from globalVariables import MESSAGE_MAX_SIZE
from modules.entities.node import Node
from modules.entities.chain import Block
import threading
import time
from queue import Queue


# 辅助函数
def user_helper():
    print("请根据提示进行输入:\n"
          "\t1. 查看当前区块链状态\n"
          "\t2. 指定执行n次节点行为\n"
          "\t3. 添加邻居节点，参数为节点的node_id ip port\n"  # 这里不允许用户更换绑定的IP和port，必须重启来达到这样的目的
          "\t4. 向邻居节点要求同步区块链\n"  # 这个理应是自动执行的，但是也允许用户手动执行
          "\t5. 发现更多邻居节点\n"  # 当活跃节点小于配置的域值时自动执行，但也允许用户手动执行。
          "\t6. 恢复/停止节点工作\n"  # 可以是挖矿、共识、智能合约，这一块必定是可编程的
          "\t7. 退出区块链\n"
          )


def blockchain_status(bc: Blockchain):
    print("自身情况简介")
    print(bc)
    print("节点情况")
    for node in bc.nodes:
        print("\t", node)
    print("链情况:")
    for block in bc.chain[-20:]:
        print("\t", block)


class SocketIO:
    """
    我个人的想法，这里从区块链基础功能函数里面把函数拉上来，然后封装成应用功能，这一部分的自由度很大，
    因此这个类的demo性更强，实践上可能需要专业人员手动进行简单的编程才能实现特定的功能。
    此外，这个类还需要担负起客户端的角色，用来进行简单的socket通信
    """
    def __init__(self, bc: Blockchain, ip, port):
        # 初始化需要一个blockchain，一个blockchain对应一个IO类
        self.blockchain = bc
        self.ip = ip
        self.port = port
        # 接收和发送消息的套接字,
        # TODO:可自定义的使用TCP或UDP进行数据传输，在C++版本中甚至可以考虑开发原始套接字,需要修改下面的senddata和recvdata,
        #  此外还要考虑消息的抗抵赖和保密性
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.bind((ip, port))
        # 两个队列中的数据都应该是字典化的
        self.work_queue = Queue()
        self.comm_queue = Queue()


    # 向某个节点发消息,data最好是json格式的字符串转的编码
    def send_data(self, node, data):
        return self.socket.sendto(data, node.node_address)

    # 接收消息:接收的也最好是json字符串
    def recv_data(self):
        data, address = self.socket.recvfrom(MESSAGE_MAX_SIZE)
        return data, address

    # 广播信息
    def broadcast(self, data):
        for node in self.blockchain.nodes:
            self.send_data(node, data)


    # TODO:这里还是写一个可编程的应用进程，毕竟单纯的从配置文件读出用户想用区块链来干什么还是有点困难的，这里就先写了一个挖矿的
    def thread_work(self):
        """
        通过消息队列来和主线程IO，当收到特定的消息时，退出线程，这里用户应当注意自己统一一下command的格式，最好是字典
        :return:
        """
        while True:
            # 从指令队列里获取信息,如果没有就 在！这！儿！停！顿！！！！！！！
            command = self.work_queue.get(timeout=None)
            if command["command"] == "Quit":
                break
            elif command["command"] == "mining":
                # 这里的count其实应该表示本次挖矿计算多少轮哈希，而非这里的计算多少块，但是因为pow函数里目前是直到找到正确答案才退出，因此这里也编程了挖矿的次数
                count = int(command["round"])
                while count > 0:
                    count = count - 1
                    # 找到当前块对应的哈希
                    block = self.blockchain.consensus()
                    # 上链
                    self.blockchain.chain[-1] = block
                    # 为链上一个空块，
                    self.blockchain.chain.append(Block(block.block_number + 1, time.time(), [], self.blockchain.hash(block), nonce=0))
            else:
                print("错误的工作指令")
                continue

    # 接收节点间数据并入列
    def thread_recv(self):
        while True:
            data, address = self.recv_data()
            # TODO: 这也太危险了吧2333333
            data_dict = eval(data.decode("utf-8"))
            data_dict["address"] = address
            try:
                self.comm_queue.put(data_dict)
            except KeyError:
                continue

    # 根据队列中接收的数据的request字段来决定调用什么函数
    def thread_handle(self):
        while True:
            item = self.comm_queue.get(timeout=None)
            # 选择想要处理的几种类型
            if "request" in item.keys():
                if item["request"] == "Record":
                    self.response_record(item)
                elif item["request"] == "Discover Neighborhood":
                    self.response_more_neighbourhood(item)
                elif item["request"] == "Synchronize Blockchain":
                    self.response_synchronize_blockchain(item)
            elif "response" in item.keys():
                if item["response"] == "Record":
                    self.handle_record(item)
                elif item["response"] == "Discover Neighborhood":
                    self.handle_more_neighbourhood(item)
                elif item["response"] == "Synchronize Blockchain":
                    self.handle_synchronize_blockchain(item)

    # 以下request和response以及handle开头的函数都是可编程的，
    # request和response都是相对应本节点而言的，handle则是收到形如response中的数据时，区块链要如何应对
    # 所有item都是经过utf解码和eval执行的字典对象
    def request_record(self):
        data = {"request": "Record"}
        self.broadcast(str(data).encode("utf-8"))

    def response_record(self, item):
        address = item["address"]
        response = {"response": "Record",
                    "data": "Accepted" if self.blockchain.submit(item["data"]) else "Denied"}
        self.send_data(Node("", address), response)

    def handle_record(self, item):
        pass

    def request_synchronize_blockchain(self):
        data = {"request": "Synchronize Blockchain"}
        self.broadcast(str(data).encode("utf-8"))

    def response_synchronize_blockchain(self, item):
        address = item["address"]
        response = {"response": "Synchronize Blockchain",
                    "data": [self.blockchain.chain[i].__dict__ for i in range(len(self.blockchain.chain))]}  # 同下，这里不传chain，否则还是会导致传python地址
        self.send_data(Node("", address), str(response).encode("utf-8"))

    def handle_synchronize_blockchain(self, item):
        address = item["address"]
        data = item["data"]
        chain = []
        for block_dict in data:
            temp_block = Block(0, 0, 0, 0)
            temp_block.__dict__ = block_dict
            chain.append(temp_block)
        if len(chain) > len(self.blockchain.chain) and self.blockchain.valid_chain(chain):
            # 这样发过来的chain在最后一块前的块都是合法的，但是最后一块我们本来不应该不相信他,但是由于他的链更长，
            # 因此其最近记录的交易记录我们也认为是正确的
            self.blockchain.chain = chain
        else:
            pass

    def request_more_neighbourhood(self):
        data = {"request": "Discover Neighborhood"}
        self.broadcast(str(data).encode("utf-8"))

    def response_more_neighbourhood(self, item):
        address = item["address"]
        response = {"response": "Discover Neighborhood",
                    "data": [str(self.blockchain.nodes[i]) for i in range(len(self.blockchain.nodes))]}
        # 直接使用self.blockchain.nodes会导致传送python表示地址的方式
        self.send_data(Node("", address), str(response).encode("utf-8"))

    def handle_more_neighbourhood(self, item):
        address = item["address"]
        data = item["data"]
        curr_nodes_id = [self.blockchain.nodes[i].node_id for i in range(len(self.blockchain.nodes))]
        for node_str in data:
            node_id, node_address = node_str.split(":")
            # 更新已有ID的节点
            if node_id in curr_nodes_id:
                self.blockchain.nodes[curr_nodes_id.index(node_id)].node_address = node_address
            else:
                self.blockchain.nodes.append(Node(node_id, node_address))

    # 主进程
    def run(self):
        # 这里我想的是三个线程，主线程用来从用户输入接收命令、输出结果，另外一个线程用来完成区块链需要的诸如PoW的工作，另外一个线程负责节点间数据收发等工作。
        thread_recv = threading.Thread(target=self.thread_recv,)
        thread_work = threading.Thread(target=self.thread_work,)
        thread_handle = threading.Thread(target=self.thread_handle, )
        threads = [thread_recv, thread_work, thread_handle]
        # 让子线程随主线程退出而退出
        for thread in threads:
            thread.daemon = True
            thread.start()
        while True:
            user_helper()
            command = input()
            command = command.split()
            # 手贱多按了个空格
            if not command:
                continue
            # 查看当前状态
            if command[0] == '1':
                blockchain_status(self.blockchain)
            # 向指定节点发送消息，参数为节点的node_id和massage
            elif command[0] == '2':
                command = {"command": "mining", "round": command[1]}
                self.work_queue.put(command)
            # 添加邻居节点,参数为节点的node_id ip port
            elif command[0] == '3':
                self.blockchain.nodes.append(Node(command[1], (command[2], int(command[3]))))
            # 向邻居节点要求同步区块链
            elif command[0] == '4':
                self.request_synchronize_blockchain()
            # 发现更多邻居节点
            elif command[0] == '5':
                self.request_more_neighbourhood()
            # 恢复/停止节点工作
            elif command[0] == '6':
                for thread in threads:
                    if thread.is_alive():
                        thread.join()
                        print("子线程结束")
                    if not thread.is_alive():
                        thread.start()
                        print("子线程开始")
            # 退出区块链
            elif command[0] == '7':
                data_dict = {"request": "Quit",
                             "node": "{\"node_id\": %s, \"node_address\":(\'%s\',%d)}" % (self.blockchain.node_id, self.ip, self.port)}
                self.broadcast(str(data_dict).encode("utf-8"))
                print("区块链退出")
                exit(0)
            else:
                print("unknown command, please check.")
                continue


if __name__ == '__main__':
    pass
